package cbbx_sm.decision_maker;

import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import cbbx_sm.probabilistic_model.Cluster;
import cbbx_sm.probabilistic_model.Prediction;

/**
 * Alfa Beta Decision Maker.
 * 
 * Supposed to be the smartest decision maker in this package.
 * 
 * @author Alessio Della Motta - University of California, Irvine
 *
 */
public class AlfaBetaDecisionMaker implements IDecisionMaker {
	private double alfa;
	private double beta;
	private double cut_off;
	
	public AlfaBetaDecisionMaker(double alfa, double beta, double cut_off){
		this.alfa = alfa;
		this.beta = beta;
		this.cut_off = cut_off;
	}
	
	@Override
	public Decision makeDecision(Prediction currentPrediction) {
		List<CameraConfiguration> confs = new Vector<CameraConfiguration>();
		
		Cluster[] clusters = new Cluster[currentPrediction.getClusterProbabilities().keySet().size()];
		currentPrediction.getClusterProbabilities().keySet().toArray(clusters);
		
		List<Cluster> fusedClusters = new Vector<Cluster>();
		for (int i = 0; i < clusters.length; i++){
			Cluster cluster = clusters[i];
			fusedClusters.add(i, cluster);
		}
		
		HashMap<String, List<Cluster>> map = getCameraClusters(fusedClusters);
		
		for (String cameraId : map.keySet()){
			Cluster choice = getChoiceForCamera(map.get(cameraId), currentPrediction);
			if (choice == null) continue;
			
			confs.add(new CameraConfiguration(cameraId, choice));
		}
		
		Decision decision = new Decision(confs);
		return decision;
	}

	private Cluster getChoiceForCamera(List<Cluster> clusters, Prediction currentPrediction){
		Cluster choice = null;
		double currentScore = 0.0;
		
		for (Cluster cluster : clusters){
			double currentClusterContribute = currentPrediction.getClusterProbability(cluster);
//			double otherClustersContribute = getOtherClustersContribute(clusters, cluster, currentPrediction);
			double otherClustersContribute = 1 - computeNormalizedEntropy(clusters, currentPrediction);

			double score = alfa * currentClusterContribute + beta * otherClustersContribute;
			
			if (score > cut_off && score > currentScore){
				choice = cluster;
				currentScore = score;
			}
		}
		
		return choice;
	}

	public static double computeNormalizedEntropy(List<Cluster> clusters, Prediction currentPrediction){
		// Computing entropy: sum(-p*log(p))
		double entropy = 0.0;
		for (Cluster cluster : clusters){
			double currentProb = currentPrediction.getClusterProbability(cluster);
			
			if (currentProb == 0.0) continue;
			double currentContribute = -currentProb * (Math.log(currentProb) / Math.log(2));
			entropy += currentContribute;
		}
		
		int numCluster = clusters.size();
		entropy /= (Math.log(numCluster)/Math.log(2));
		
		return entropy;
	}
	
	@Deprecated
	private double getOtherClustersContribute(List<Cluster> clusters, Cluster exception, Prediction currentPrediction){
		double contribute = 1.0;
		for (Cluster cluster : clusters)
			if (cluster != exception)
				contribute *= (1.0 - currentPrediction.getClusterProbability(cluster));
		
		return contribute;
	}
	
	private HashMap<String, List<Cluster>> getCameraClusters(List<Cluster> fusedClusters){
		HashMap<String, List<Cluster>> map = new HashMap<String, List<Cluster>>();
		for (Cluster c : fusedClusters){
			if (!map.containsKey(c.getCameraId()))
				map.put(c.getCameraId(), new Vector<Cluster>());
			map.get(c.getCameraId()).add(c);
		}
		
		return map;
	}
	
	@Override
	public String toString(){
		return "" + this.getClass().getSimpleName() + "\t" + cut_off + "\t" + alfa + "\t" + beta;
	}
}
